#include "iap/include/iap.h"
#include "iap/include/ymodem.h"
#include "iap/include/md5.h"
#include <functional>
#include <stdint.h>
#include <QTime>

static Iap * __iap;

void IapDefault_ShowMsg(QString msg)
{
    qDebug() << msg << "\n";
}

void SetIap(Iap * iap)
{
    __iap = iap;
}

int32_t C_ReadIo(uint8_t * pdata, int32_t len)
{
    return __iap->ReadIo(pdata, len);
}

int32_t C_WriteIo(uint8_t * pdata, int32_t len)
{
    return __iap->WriteIo(pdata, len);
}

int32_t C_ReadHd(uint8_t * pdata, int32_t len)
{
    return __iap->ReadHd(pdata, len);
}

int32_t C_WriteHd(uint8_t * pdata, int32_t len)
{
    return __iap->WriteHd(pdata, len);
}



Iap::Iap(std::function<void(QString)> showMsg) :
    __hd(),
    __io()
{
    Init();
    SetIap(this);
    __showMsg = showMsg;
}

Iap::~Iap()
{
    __hd.Close();
    __io.Close();
}

bool Iap::SetHd(const QString &name)
{
    __hd.SetFile(name);
    return __hd.Open();
}

bool Iap::SetIo(QHostAddress localIp, quint16 localPort, QHostAddress sendIp, quint16 sendPort)
{
    __io.SetSendAddr(sendIp, sendPort);
    return __io.BindLocalAddr(localIp, localPort);
}

void Iap::Init()
{
    Ymodem_Init(C_ReadIo, C_WriteIo, C_ReadHd, C_WriteHd);
}

void Iap::Close()
{
    __io.Close();
    __hd.Close();
}

int32_t Iap::ReadIo(uint8_t * pdata, int32_t len)
{
    return __io.Read((char *)pdata, len, nullptr, nullptr, 100);
}

int32_t Iap::WriteIo(uint8_t * pdata, int32_t len)
{
    QByteArray buf = QByteArray::fromRawData((char *)pdata, len);
    return __io.Write(buf);
}

int32_t Iap::ReadHd(uint8_t * pdata, int32_t len)
{
    return __hd.Read((char *)pdata, len);
}

int32_t Iap::WriteHd(uint8_t * pdata, int32_t len)
{
    QByteArray data = QByteArray::fromRawData((char *)pdata, len);
    return __hd.Write(data);
}

QByteArray Iap::Md5()
{
    char buf[1024];
    QByteArray md5(16, 0);
    mbedtls_md5_context ctxMd5;
    mbedtls_md5_init(&ctxMd5);
    mbedtls_md5_starts(&ctxMd5);
    qint64 len = 0;
    __hd.FromBegin();
    do {
        len = __hd.Read(buf, 1024);
//        qDebug() << len;
        mbedtls_md5_update(&ctxMd5, buf, len);
    } while(len);
    mbedtls_md5_finish(&ctxMd5, md5.data());
    return std::move(md5);
}

qint32 Iap::Send()
{
    QTime to = QTime::currentTime().addSecs(10);
    qint32 retval = 0;
    bool isOK = false;

    do {
        __io.Write("$iapc_dl*");
        isOK = __WaitForData("Download image", 1000);
    } while(!isOK && QTime::currentTime() < to);
    if (!isOK) {
        __showMsg("Time Out. Not connected");
        return -1;
    }

    std::string name = __hd.GetFilename().toStdString();
    char * ptrName = const_cast<char*>(name.c_str());
    QByteArray md5 = Md5();
    __showMsg("- Download Info :");
    __showMsg("  name : " + __hd.GetFilename());
    __showMsg("  size : " + QString("%1").arg(__hd.Size()));
    __showMsg("  md5  : " + __ToHexString(md5));

    __hd.FromBegin();
    if (Ymodem_Transmit(ptrName, __hd.Size(), md5.data()) != Ymodem_SendDone) {
        retval = -3;
    }

    to = QTime::currentTime().addSecs(10);
    do {
        isOK = __WaitForData("DONE", 1000);
    } while(!isOK && QTime::currentTime() < to);

    if (!isOK) {
        __showMsg("Time Out");
    }
    return retval;
}

qint32 Iap::Recv()
{
    QTime to = QTime::currentTime().addSecs(10);
    qint32 retval = 0;
    bool isOK = false;

    do {
        __io.Write("iap");
        isOK = __WaitForData("Enter", 1000);
    } while(!isOK && QTime::currentTime() < to);
    if (!isOK) {
        __showMsg("Time Out. Not connected");
        return -1;
    }

    to = QTime::currentTime().addSecs(5);
    do {
        __io.Write("iap_ul");
        isOK = __WaitForData("Uploading image", 1000);
    } while(!isOK && QTime::currentTime() < to);
    if (!isOK) {
        __showMsg("Time Out. Dose not into the download step");
        return -2;
    }

    __hd.FromBegin();
    if (Ymodem_Receive() != Ymodem_RecvDone) {
        retval = 1;
    }

    __WaitForData("xxx", 1000);
}

bool Iap::__WaitForData(QByteArray tData, qint32 timeout)
{
    QTime to = QTime::currentTime().addMSecs(timeout);
    QByteArray rbuf(256, 0);
    qint64 rlen = 0;
    bool isReceived = false;
    do {
        rlen = __io.Read(rbuf.data(), rbuf.size(), nullptr, nullptr, 10);
        isReceived = __FindData(rbuf, rlen, tData);
        if (rlen > 0) {
            rbuf[rlen] = 0;
            __showMsg(rbuf);
            rbuf.fill(0);
        }
    } while(!isReceived && QTime::currentTime() < to);
    return isReceived;
}

bool Iap::__FindData(QByteArray & rData, qint32 len, QByteArray & tData)
{
    static bool findHead = false;
    static qint32 index  = 0;
    bool isFound = false;
    for (int i = 0; i < len ; ++i) {
        if (!findHead && rData[i] == tData[0]) {
            findHead = true;
            index = 1;
        } else if (findHead && rData[i] == tData[index]) {
            ++index;
        } else {
            findHead = false;
            index = 0;
        }

        if (index == tData.size()) {
            isFound = true;
            findHead = false;
            index = 0;
        }
    }
    return isFound;
}

QString Iap::__ToHexString(QByteArray md5)
{
    QString str;
    for (auto m : md5) {
        str += QString("%1").arg((unsigned char)m, 2, 16, QChar('0'));
    }
    str = str.toUpper();
    return std::move(str);
}
